<template>
  <div class="bom-entry-page">
    <header class="page-header">
      <div class="title-wrap">
        <h2>BOM条目</h2>
        <span class="sub">维护成本组成 / 费用计算方式</span>
      </div>
      <div class="actions">
        <div class="search-box">
          <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <circle cx="11" cy="11" r="8"/>
            <path d="m21 21-4.35-4.35"/>
          </svg>
          <input v-model="keyword" placeholder="搜索名称 / 产品名称" @input="debouncedSearch(1)" />
        </div>
        <select v-model="filterMode" class="select">
          <option value="">费用模式(全部)</option>
          <option value="fixed">固定费用</option>
          <option value="count">计数费用</option>
        </select>
        <button class="btn primary elevate" @click="openCreate">
          <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <line x1="12" y1="5" x2="12" y2="19"/>
            <line x1="5" y1="12" x2="19" y2="12"/>
          </svg>
          新增条目
        </button>
      </div>
    </header>

    <section v-if="filtered.length" class="table-wrapper card">
      <table class="entry-table fancy">
        <thead>
          <tr>
            <th style="width:160px">名称</th>
            <th style="width:120px">BOM类型</th>
            <th style="width:160px">产品归属</th>
            <th style="width:110px">费用模式</th>
            <th style="width:140px">创建时间</th>
            <th style="width:140px">修改时间</th>
            <th style="width:160px">操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="e in filtered" :key="e.id">
            <td>{{ e.costName }}</td>
            <td>{{ formatBomType(e.bomType) }}</td>
            <td>{{ e.productName }}</td>
            <td class="mode-cell">
              <button class="btn view sm" @click="openModeViewer(e)" title="查看费用模式" aria-label="查看费用模式">
                <span class="eye" aria-hidden="true">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" width="14" height="14">
                    <rect x="3" y="4" width="18" height="16" rx="2" ry="2" />
                    <line x1="3" y1="10" x2="21" y2="10" />
                    <line x1="8" y1="4" x2="8" y2="20" />
                    <line x1="14" y1="4" x2="14" y2="20" />
                  </svg>
                </span>
                <span class="txt">查看</span>
              </button>
            </td>
            <td>{{ formatDate(e.createdAt) }}</td>
            <td>{{ formatDate(e.updatedAt) }}</td>
            <td class="ops">
              <button class="btn ghost sm blue" @click="edit(e)" :disabled="e.__remote" :title="e.__remote ? '远程数据暂不支持前端直接编辑' : '编辑'">
                <svg class="btn-icon pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                  <path d="M12 20h9" />
                  <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5Z" />
                </svg>
                编辑
              </button>
              <button class="btn ghost sm danger" @click="remove(e.id)" :disabled="e.__remote" :title="e.__remote ? '远程数据暂不支持前端直接删除' : '删除'">
                <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                  <polyline points="3,6 5,6 21,6"/>
                  <path d="m19,6v14a2,2,0,0,1-2,2H7a2,2,0,0,1-2-2V6m3,0V4a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2v2"/>
                  <line x1="10" y1="11" x2="10" y2="17"/>
                  <line x1="14" y1="11" x2="14" y2="17"/>
                </svg>
                删除
              </button>
            </td>
          </tr>
        </tbody>
      </table>
    </section>
    <p v-else class="empty-tip" v-if="!loading && !error">暂无条目</p>
    <p v-if="error" class="empty-tip" style="color:#dc2626">{{ error }}</p>
    <div v-if="loading" class="empty-tip">加载中...</div>
    <div class="pagination-bar" v-if="total>0 && !loading">
      <button class="btn ghost sm" :disabled="page===1" @click="loadList(page-1)">上一页</button>
      <span class="pager-info">第 {{ page }} / {{ totalPages }} 页 (共 {{ total }} 条)</span>
      <button class="btn ghost sm" :disabled="page===totalPages" @click="loadList(page+1)">下一页</button>
    </div>

    <dialog ref="dialogRef" class="entry-dialog">
      <form method="dialog" class="dialog-body" @submit.prevent="save">
        <div class="dialog-header">
          <h3>
            <svg class="dialog-title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
              <line x1="8" y1="21" x2="16" y2="21"/>
              <line x1="12" y1="17" x2="12" y2="21"/>
            </svg>
            {{ current?.id ? '编辑条目' : '新增条目' }}
          </h3>
          <button type="button" class="close-x" @click="closeDialog" aria-label="关闭">
            <svg class="close-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <line x1="18" y1="6" x2="6" y2="18"/>
              <line x1="6" y1="6" x2="18" y2="18"/>
            </svg>
          </button>
        </div>
        <div class="form-grid enhanced">
          <label>名称
            <input v-model="form.costName" :disabled="isEdit" :class="{ readonly: isEdit }" required />
          </label>
          <label>
            BOM类型
            <template v-if="!isEdit">
              <select v-model="form.bomType" required>
                <option value="product">产品BOM</option>
                <option value="package">包装BOM</option>
              </select>
            </template>
            <template v-else>
              <input :value="formatBomType(form.bomType)" disabled class="readonly" />
            </template>
          </label>
          <label>产品归属
            <select v-model="form.productName" :disabled="isEdit" :class="{ readonly: isEdit }" required>
              <option value="">请选择产品</option>
              <option v-for="p in productOptions" :key="p" :value="p">{{ p }}</option>
            </select>
          </label>
          <div class="mode-switch" style="grid-column: 1 / -1;">
            <p class="field-label">费用模式</p>
            <div class="mode-options" role="radiogroup" aria-label="费用模式">
              <button type="button" class="mode-option" :class="{ active: form.costMode==='fixed' }" @click="form.costMode='fixed'">固定费用</button>
              <button type="button" class="mode-option" :class="{ active: form.costMode==='count' }" @click="form.costMode='count'">计数费用</button>
            </div>
          </div>
          <label>是否使用公式
            <select v-model="form.useFormula">
              <option :value="false">否</option>
              <option :value="true">是</option>
            </select>
          </label>
          <transition name="formula-toggle" mode="out-in">
            <div v-if="form.useFormula" key="formula" class="formula-inline" aria-live="polite">
              <button type="button" class="btn secondary elevate appear-pop" @click="openFormulaMaintainFromForm">
                <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                  <path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/>
                  <path d="M9 10h6"/>
                  <path d="M9 14h4"/>
                </svg>
                公式维护
              </button>
              <div class="formula-preview" v-if="form.formulaExpression">
                <span class="preview-label">预览：</span>
                <code class="preview-code" :title="form.formulaExpression">{{ form.formulaExpression }}</code>
              </div>
              <div class="formula-preview empty" v-else>暂无公式</div>
            </div>
            <label v-else key="price" style="grid-column: 1 / -1; display:flex; flex-direction:column; gap:.4rem;">
              单价
              <input type="number" step="0.01" min="0" v-model.number="form.unitPrice" required />
            </label>
          </transition>
          <div class="hint" style="grid-column:1/-1;">
            <p class="hint-text" v-if="!form.useFormula">若使用“计数费用”，单价将按数量 * 单价（数量后续由业务上下文提供）。当前仅维护单价。</p>
            <p class="hint-text" v-else>使用公式时，不需要填写单价，可在“公式维护”中编辑表达式。</p>
          </div>
        </div>
        <div class="dialog-actions">
          <button class="btn ghost" value="cancel" @click="closeDialog">
            <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
              <path d="m6 9 6 6 6-6"/>
            </svg>
            取消
          </button>
          <button class="btn primary elevate" type="submit" :disabled="!canSubmit">
            <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
              <polyline points="20,6 9,17 4,12"/>
            </svg>
            保存
          </button>
        </div>
      </form>
    </dialog>

    <!-- 费用模式查看弹窗组件 -->
    <CostModeViewer ref="modeViewerRef" />
    <!-- 公式维护弹窗组件 -->
    <FormulaMaintainDialog ref="formulaMaintainRef" @save="saveFormula" />
  </div>
</template>
/* moved misplaced styles for .entry-dialog into style section below */

<script setup lang="ts">
import { ref, computed } from 'vue';
import { useProductsForBom } from '../../store';
import CostModeViewer from './components/CostModeViewer.vue';
import FormulaMaintainDialog from './components/FormulaMaintainDialog.vue';
import { fetchBomList } from '../../services/bom';

// BOM 条目数据结构
interface BomEntryRow {
  id: string;
  costName: string;
  bomType: 'product' | 'package';
  productName: string; // productType
  costMode: 'fixed' | 'count';
  useFormula: boolean;
  formulaExpression?: string;
  fixedAmount?: number;
  unitPrice?: number;
  quantity?: number;
  createdAt: number;
  updatedAt: number;
  __remote?: boolean;
}

const productOptions = useProductsForBom();
const keyword = ref('');
const filterMode = ref('');

// 分页状态
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const totalPages = computed(()=> Math.max(1, Math.ceil(total.value / pageSize.value)));
const loading = ref(false);
const error = ref('');
const records = ref<BomEntryRow[]>([]);

// 防抖搜索计时器
let searchDebounceTimer: number | null = null;
const DEBOUNCE_DELAY = 500; // 毫秒

async function loadList(targetPage?: number){
  if(targetPage) page.value = targetPage;
  loading.value = true; error.value='';
  try {
    const kw = keyword.value.trim();
    const data = await fetchBomList({ page: page.value, pageSize: pageSize.value, invCode: kw, invName: kw });
    total.value = data.total; pageSize.value = data.size; page.value = data.current;
    records.value = (data.records||[]).map((r:any) => {
      const createdTs = r.createTime ? Date.parse(r.createTime.replace(/-/g,'/')) : Date.now();
      const updatedTs = r.updateTime ? Date.parse(r.updateTime.replace(/-/g,'/')) : createdTs;
      const feeAmountNum = typeof r.feeAmount === 'string' ? parseFloat(r.feeAmount) : (r.feeAmount || 0);
      return {
        id: r.id,
        costName: r.name || r.invName || '未命名',
        bomType: r.bomType==='package' ? 'package':'product',
        productName: r.productType || '—',
        costMode: r.feeModel==='count' ? 'count':'fixed',
        unitPrice: r.feeModel==='count' ? feeAmountNum : undefined,
        fixedAmount: r.feeModel!=='count' ? feeAmountNum : undefined,
        createdAt: createdTs,
        updatedAt: updatedTs,
        useFormula: false,
        __remote: true
      } as BomEntryRow;
    });
  } catch(e:any){ error.value = e.message || '加载失败'; }
  finally { loading.value = false; }
}

// 防抖搜索函数
function debouncedSearch(targetPage: number = 1) {
  if (searchDebounceTimer !== null) {
    clearTimeout(searchDebounceTimer);
  }
  searchDebounceTimer = window.setTimeout(() => {
    loadList(targetPage);
    searchDebounceTimer = null;
  }, DEBOUNCE_DELAY);
}

loadList(1);

// 本地过滤（费用模式）
const filtered = computed(()=> records.value.filter(r => !filterMode.value || r.costMode===filterMode.value));

const dialogRef = ref<HTMLDialogElement | null>(null);
const modeViewerRef = ref<InstanceType<typeof CostModeViewer> | null>(null);
const formulaMaintainRef = ref<InstanceType<typeof FormulaMaintainDialog> | null>(null);
const current = ref<BomEntryRow | null>(null);

const form = ref({
  costName: '',
  bomType: 'product' as 'product' | 'package',
  productName: '',
  costMode: 'fixed' as 'fixed' | 'count',
  useFormula: false as boolean,
  formulaExpression: '',
  unitPrice: 0
});

const isEdit = computed(() => !!current.value);

const canSubmit = computed(() => {
  if (!form.value.costName || !form.value.productName) return false;
  if (form.value.useFormula) return true; // 允许先只选择使用公式
  return form.value.unitPrice >= 0;
});

function openCreate() {
  current.value = null;
  form.value = { costName: '', bomType: 'product', productName: '', costMode: 'fixed', useFormula: false, formulaExpression: '', unitPrice: 0 };
  const el = dialogRef.value;
  if (el) {
    el.classList.remove('closing');
    el.showModal();
  }
}
function edit(e: BomEntryRow) {
  current.value = e;
  form.value = { costName: e.costName, bomType: e.bomType || 'product', productName: e.productName, costMode: e.costMode, useFormula: e.useFormula, formulaExpression: e.formulaExpression || '', unitPrice: (e.costMode==='fixed' ? (e.fixedAmount ?? 0) : (e.unitPrice ?? 0)) };
  const el = dialogRef.value;
  if (el) {
    el.classList.remove('closing');
    el.showModal();
  }
}
function closeDialog() {
  const el = dialogRef.value;
  if (!el) return;
  if (el.classList.contains('closing')) return;
  el.classList.add('closing');
  const done = () => {
    el.removeEventListener('transitionend', done);
    el.classList.remove('closing');
    el.close();
  };
  el.addEventListener('transitionend', done);
  setTimeout(done, 450);
}

function save() {
  if (!canSubmit.value) return;
  // 临时新增本地行（后端新增接口未接入），刷新即失
  const now = Date.now();
  records.value.unshift({
    id: Math.random().toString(36).slice(2),
    createdAt: now,
    updatedAt: now,
    costName: form.value.costName,
    bomType: form.value.bomType,
    productName: form.value.productName,
    costMode: form.value.costMode,
    useFormula: form.value.useFormula,
    formulaExpression: form.value.formulaExpression || undefined,
    fixedAmount: form.value.useFormula ? undefined : (form.value.costMode==='fixed' ? form.value.unitPrice : undefined),
    unitPrice: form.value.useFormula ? undefined : (form.value.costMode==='count' ? form.value.unitPrice : undefined),
    quantity: undefined,
    __remote: false
  });
  closeDialog();
}
function remove(id: string) { records.value = records.value.filter(e => e.id !== id || e.__remote); }

function openModeViewer(e: BomEntryRow) {
  modeViewerRef.value?.open(e);
}
function saveFormula(payload: { expression: string; comment?: string; params?: string[] }) {
  if (current.value) {
    current.value.formulaExpression = payload.expression;
    current.value.updatedAt = Date.now();
    // 若正在编辑此条，表单同步
    if (form.value.costName === current.value.costName) {
      form.value.formulaExpression = payload.expression;
    }
  } else {
    // 若没有 current 但在新增 dialog 中
    form.value.formulaExpression = payload.expression;
  }
}

function openFormulaMaintainFromForm() {
  formulaMaintainRef.value?.open({ expression: form.value.formulaExpression, params: [] });
}
function formatBomType(t?: 'product' | 'package') { return t === 'package' ? '包装BOM' : '产品BOM'; }
function formatDate(ts: number) { const d = new Date(ts); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`; }
 </script>

<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

* { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; }

.bom-entry-page { 
  display: flex; 
  flex-direction: column; 
  gap: 1.2rem; 
  animation: pageSlideIn 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes pageSlideIn {
  from { 
    opacity: 0; 
    transform: translateY(20px); 
  }
  to { 
    opacity: 1; 
    transform: translateY(0); 
  }
}

.page-header { 
  display: flex; 
  flex-wrap: wrap; 
  justify-content: space-between; 
  align-items: flex-end; 
  gap: 1rem; 
  padding: 1rem 1.25rem 1.1rem; 
  background: linear-gradient(145deg, #ffffff, #f8fafc); 
  border: 1px solid #e2e8f0; 
  border-radius: 16px; 
  box-shadow: 0 4px 20px -4px rgba(15,23,42,.15), 0 2px 8px -2px rgba(15,23,42,.1); 
  position: relative;
  overflow: hidden;
}

.page-header::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, #3b82f6, #8b5cf6, #06b6d4);
  opacity: 0.8;
}

.title-wrap { 
  display: flex; 
  flex-direction: column; 
  gap: 0.4rem; 
}

.title-wrap h2 { 
  margin: 0; 
  font-size: 24px; 
  font-weight: 700; 
  letter-spacing: -0.025em; 
  color: #0f172a; 
  background: linear-gradient(135deg, #1e293b, #475569);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.title-wrap .sub { 
  font-size: 13px; 
  color: #64748b; 
  letter-spacing: 0.01em; 
  font-weight: 500; 
}

.actions { 
  display: flex; 
  gap: 0.75rem; 
  align-items: center; 
  flex-wrap: wrap; 
}

.search-box { 
  display: flex; 
  align-items: center; 
  gap: 0.5rem; 
  padding: 0.6rem 0.8rem; 
  border: 1px solid #cbd5e1; 
  border-radius: 12px; 
  background: #fff; 
  min-width: 260px; 
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 
  position: relative;
}

.search-box:focus-within { 
  border-color: #3b82f6; 
  box-shadow: 0 0 0 4px rgba(59,130,246,.15), 0 4px 12px -2px rgba(59,130,246,.2); 
  transform: translateY(-1px);
}

.search-icon { 
  width: 18px; 
  height: 18px; 
  color: #6b7280; 
  transition: color 0.3s;
}

.search-box:focus-within .search-icon {
  color: #3b82f6;
}

.search-box input { 
  border: none; 
  outline: none; 
  font-size: 14px; 
  flex: 1; 
  font-weight: 500;
  color: #374151;
}

.search-box input::placeholder {
  color: #9ca3af;
  font-weight: 400;
}

.select { 
  padding: 0.6rem 0.8rem; 
  border: 1px solid #cbd5e1; 
  border-radius: 12px; 
  background: #fff; 
  font-size: 14px; 
  min-width: 160px; 
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 
  font-weight: 500;
}

.select:focus { 
  border-color: #3b82f6; 
  box-shadow: 0 0 0 4px rgba(59,130,246,.15); 
  transform: translateY(-1px);
}

.btn { 
  position: relative; 
  cursor: pointer; 
  border: 1px solid transparent; 
  padding: 0.65rem 1.2rem; 
  border-radius: 12px; 
  font-size: 14px; 
  font-weight: 600; 
  letter-spacing: 0.025em; 
  line-height: 1; 
  display: inline-flex; 
  align-items: center; 
  gap: 0.5rem; 
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 
  overflow: hidden;
}

.btn-icon {
  width: 16px;
  height: 16px;
  transition: transform 0.2s;
}

.btn:hover .btn-icon {
  transform: scale(1.1) rotate(2deg);
}

.btn.primary .btn-icon {
  filter: drop-shadow(0 2px 4px rgba(0,0,0,.1));
}

.btn.primary:hover .btn-icon {
  transform: scale(1.15) rotate(-2deg);
}

.btn.primary { 
  background: linear-gradient(135deg, #3b82f6, #2563eb); 
  color: #fff; 
  border-color: #2563eb; 
}

.btn.primary:hover { 
  background: linear-gradient(135deg, #2563eb, #1d4ed8); 
  transform: translateY(-2px);
}

.btn.primary:active { 
  transform: translateY(1px) scale(0.98);
}

/* Loading button state */
.btn.loading {
  pointer-events: none;
  opacity: 0.7;
}

.btn.loading::after {
  content: '';
  position: absolute;
  width: 16px;
  height: 16px;
  margin: auto;
  border: 2px solid transparent;
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.btn.ghost { 
  background: rgba(255,255,255,0.8); 
  border: 1px solid #e2e8f0; 
  color: #374151; 
}

.btn.ghost:hover { 
  background: #f8fafc; 
  border-color: #cbd5e1;
  transform: translateY(-1px);
}

.btn.ghost.danger { 
  color: #dc2626; 
}

.btn.ghost.danger:hover { 
  background: #fef2f2; 
  border-color: #fecaca; 
}

.btn.ghost.blue { 
  color: #2563eb; 
}

.btn.ghost.blue:hover { 
  background: #eff6ff; 
  border-color: #bfdbfe; 
}

.btn.sm { 
  padding: 0.5rem 0.8rem; 
  font-size: 13px; 
  border-radius: 10px; 
}

.btn.elevate { 
  box-shadow: 0 4px 14px -2px rgba(59,130,246,.4), 0 2px 6px -1px rgba(59,130,246,.2); 
}

.btn.elevate:hover { 
  box-shadow: 0 8px 25px -3px rgba(59,130,246,.4), 0 4px 12px -2px rgba(59,130,246,.3); 
}

.btn.view { 
  position: relative; 
  background: linear-gradient(135deg, #ffffff, #f8fafc); 
  border: 1px solid #e2e8f0; 
  color: #374151; 
  padding: 0.5rem 0.9rem; 
  border-radius: 20px; 
  overflow: hidden; 
}

.btn.view .eye { 
  font-size: 14px; 
  line-height: 1; 
}

.btn.view .eye svg { 
  display: block; 
}

.btn.view .txt { 
  font-size: 12px; 
  font-weight: 600; 
  letter-spacing: 0.025em; 
}

.btn.view:before { 
  content: ""; 
  position: absolute; 
  inset: 0; 
  background: radial-gradient(circle at 30% 30%, rgba(59,130,246,.15), transparent 60%); 
  opacity: 0; 
  transition: opacity 0.4s; 
}

.btn.view:hover:before { 
  opacity: 1; 
}

.btn.view:hover { 
  border-color: #3b82f6; 
  color: #1e40af; 
  box-shadow: 0 4px 12px -2px rgba(59,130,246,.3); 
}

.table-wrapper.card { 
  border: 1px solid #e2e8f0; 
  border-radius: 16px; 
  background: #fff; 
  box-shadow: 0 8px 25px -6px rgba(15,23,42,.1), 0 3px 10px -2px rgba(15,23,42,.05); 
  padding: 0.5rem 0.8rem 0.9rem; 
  overflow: hidden;
  animation: tableSlideIn 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.2s both;
}

@keyframes tableSlideIn {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.entry-table { 
  width: 100%; 
  border-collapse: separate; 
  border-spacing: 0; 
  font-size: 14px; 
}

.entry-table.fancy thead th { 
  background: linear-gradient(135deg, #f8fafc, #f1f5f9); 
  font-weight: 700; 
  font-size: 12px; 
  letter-spacing: 0.05em; 
  text-transform: uppercase; 
  color: #475569; 
  position: sticky; 
  top: 0; 
  z-index: 1; 
  border-bottom: 2px solid #e2e8f0;
}

.entry-table th, .entry-table td { 
  padding: 0.8rem 0.9rem; 
  text-align: left; 
}

.entry-table tbody tr { 
  border-radius: 8px; 
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  animation: tableRowSlideIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.entry-table tbody tr:nth-child(1) { animation-delay: 0.1s; }
.entry-table tbody tr:nth-child(2) { animation-delay: 0.2s; }
.entry-table tbody tr:nth-child(3) { animation-delay: 0.3s; }
.entry-table tbody tr:nth-child(4) { animation-delay: 0.4s; }
.entry-table tbody tr:nth-child(5) { animation-delay: 0.5s; }

@keyframes tableRowSlideIn {
  from {
    opacity: 0;
    transform: translateX(-20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

.entry-table tbody tr:hover { 
  background: linear-gradient(135deg, #f8fafc, #f1f5f9); 
  transform: translateX(4px);
  box-shadow: 0 4px 12px -2px rgba(59,130,246,.15);
  border-left: 3px solid var(--primary-500);
}

.entry-table tbody tr + tr td { 
  border-top: 1px solid #f1f5f9; 
}

.ops { 
  display: flex; 
  gap: 0.5rem; 
}

.empty-tip { 
  color: #64748b; 
  text-align: center; 
  padding: 3rem 0; 
  font-size: 16px;
  font-weight: 500;
}

.entry-dialog::backdrop { 
  background: rgba(15,23,42,.5); 
  backdrop-filter: blur(8px); 
  animation: backdropFadeIn 0.4s ease; 
}

@keyframes backdropFadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.entry-dialog { 
  --ease-silk: cubic-bezier(.4,0,.2,1); 
  border: none; 
  padding: 0; 
  border-radius: 20px; 
  width: 780px; 
  max-width: 95vw; 
  box-shadow: 0 20px 50px -6px rgba(15,23,42,.4), 0 8px 25px -4px rgba(15,23,42,.2); 
  overflow: hidden; 
  transition: opacity .26s var(--ease-silk), transform .3s var(--ease-silk), filter .3s var(--ease-silk); 
  will-change: transform, opacity, filter; 
}
.entry-dialog.closing { 
  opacity: 0; 
  transform: translateY(-10px) scale(.95); 
  filter: blur(3px) saturate(.9); 
  pointer-events: none; 
}
.entry-dialog.closing::backdrop { background: rgba(15,23,42,0); backdrop-filter: blur(0); }
.entry-dialog[open] { animation: dialogSlideIn .34s var(--ease-silk); }
@keyframes dialogSlideIn { 0% {opacity:0; transform: translateY(14px) scale(.94); filter: blur(4px);} 100% {opacity:1; transform: translateY(0) scale(1); filter: blur(0);} }

.dialog-body { 
  display: flex; 
  flex-direction: column; 
  gap: 1.2rem; 
  padding: 1.5rem 1.75rem 1.75rem; 
  background: linear-gradient(145deg, #ffffff, #f8fafc 60%, #f1f5f9); 
}

.dialog-header { 
  display: flex; 
  justify-content: space-between; 
  align-items: center; 
  padding: 0 0 0.5rem; 
  border-bottom: 1px solid #e2e8f0;
}

.dialog-header h3 { 
  margin: 0; 
  font-size: 20px; 
  font-weight: 700; 
  letter-spacing: -0.025em; 
  color: #0f172a; 
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.dialog-title-icon {
  width: 24px;
  height: 24px;
  color: #3b82f6;
}

.close-x { 
  background: transparent; 
  border: none; 
  cursor: pointer; 
  padding: 0.5rem; 
  border-radius: 10px; 
  color: #6b7280; 
  transition: all 0.2s; 
  display: flex;
  align-items: center;
  justify-content: center;
}

.close-icon {
  width: 20px;
  height: 20px;
}

.close-x:hover { 
  background: linear-gradient(135deg, #fee2e2, #fecaca); 
  color: #dc2626;
  transform: rotate(90deg);
}

.close-x:active {
  transform: rotate(90deg) scale(0.9);
}

.form-grid { 
  display: grid; 
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); 
  gap: 1rem; 
}

.form-grid label { 
  display: flex; 
  flex-direction: column; 
  font-size: 13px; 
  gap: 0.4rem; 
  color: #374151; 
  font-weight: 600;
}

.form-grid input, .form-grid select { 
  padding: 0.75rem 0.9rem; 
  border: 1px solid #d1d5db; 
  border-radius: 12px; 
  font-size: 14px; 
  background: #fff; 
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 
  font-weight: 500;
}

.form-grid input:focus, .form-grid select:focus { 
  border-color: #3b82f6; 
  box-shadow: 0 0 0 4px rgba(59,130,246,.1); 
  outline: none; 
  transform: translateY(-1px);
}

.form-grid input.readonly, .form-grid select.readonly { 
  background: #f9fafb; 
  cursor: not-allowed; 
  opacity: 0.7;
}

.mode-switch { 
  display: flex; 
  flex-direction: column; 
  gap: 0.7rem; 
}

.field-label { 
  margin: 0; 
  font-size: 13px; 
  font-weight: 700; 
  color: #374151; 
  letter-spacing: 0.025em; 
}

.mode-options { 
  display: inline-flex; 
  gap: 0.8rem; 
  flex-wrap: wrap; 
}

.mode-option { 
  border: 2px solid #e5e7eb; 
  background: #fff; 
  padding: 0.7rem 1.2rem; 
  border-radius: 25px; 
  font-size: 13px; 
  cursor: pointer; 
  line-height: 1; 
  box-shadow: 0 2px 4px rgba(0,0,0,.05); 
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 
  font-weight: 600; 
  letter-spacing: 0.025em; 
  position: relative;
}

.mode-option:hover { 
  background: #f8fafc; 
  border-color: #cbd5e1;
  transform: translateY(-2px);
}

.mode-option.active { 
  background: linear-gradient(135deg, #3b82f6, #2563eb); 
  color: #fff; 
  border-color: #2563eb; 
  box-shadow: 0 4px 12px -1px rgba(59,130,246,.4); 
}

.formula-inline { 
  grid-column: 1 / -1; 
  display: flex; 
  align-items: stretch; 
  gap: 0.9rem; 
  flex-wrap: nowrap; 
  min-height: 46px;
}
.formula-inline > .btn { flex-shrink: 0; }
.formula-inline .formula-preview { 
  flex: 0 1 480px; 
  max-width: 480px; 
  min-width: 240px; 
  white-space: nowrap; 
  overflow: hidden; 
  text-overflow: ellipsis; 
}
.formula-inline .formula-preview.empty { 
  display: flex; 
  align-items: center; 
  padding-left: 0.9rem; 
  padding-right: 0.9rem; 
  background: #f8fafc; 
  border: 1px dashed #cbd5e1; 
  font-size: 12px; 
  font-style: normal; 
}
.preview-code { 
  display: inline-block; 
  max-width: calc(100% - 42px); 
  vertical-align: middle; 
  white-space: nowrap; 
  overflow: hidden; 
  text-overflow: ellipsis; 
}
@media (max-width: 760px) { 
  .formula-inline { flex-direction: column; align-items: flex-start; }
  .formula-inline .formula-preview, .formula-inline .formula-preview.empty { width: 100%; max-width: 100%; white-space: normal; }
  .preview-code { max-width: 100%; white-space: normal; }
}

.formula-preview { 
  font-size: 12px; 
  background: linear-gradient(135deg, #f8fafc, #f1f5f9); 
  border: 1px solid #e2e8f0; 
  padding: 0.7rem 0.9rem; 
  border-radius: 10px; 
  display: flex; 
  align-items: center; 
  gap: 0.4rem; 
}

.formula-preview.empty { 
  color: #6b7280; 
  font-style: italic; 
}

.preview-code { 
  font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, monospace; 
  font-size: 12px; 
  color: #1e40af;
  font-weight: 600;
}

/* 出现/消失过渡：公式维护块 */
.formula-toggle-enter-from, .formula-toggle-leave-to { 
  opacity: 0; 
  transform: translateY(-6px) scale(.96); 
  filter: blur(2px);
}
.formula-toggle-leave-from, .formula-toggle-enter-to { 
  opacity: 1; 
  transform: translateY(0) scale(1); 
  filter: blur(0);
}
.formula-toggle-enter-active, .formula-toggle-leave-active { 
  transition: all .38s cubic-bezier(.34,1.56,.64,1), filter .3s ease; 
}
.appear-pop { 
  animation: btnPop .55s cubic-bezier(.34,1.56,.64,1);
}
@keyframes btnPop { 
  0% { transform: scale(.4) rotate(-12deg); opacity: 0; }
  50% { transform: scale(1.08) rotate(2deg); opacity: .9; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}

.btn.secondary { 
  background: linear-gradient(135deg, #f8fafc, #f1f5f9); 
  border-color: #cbd5e1; 
  color: #475569;
}

.btn.secondary:hover { 
  background: linear-gradient(135deg, #f1f5f9, #e2e8f0); 
}

.hint-text { 
  margin: 0; 
  font-size: 12px; 
  color: #6b7280; 
  line-height: 1.5;
}

.dialog-actions { 
  display: flex; 
  justify-content: flex-end; 
  gap: 1rem; 
  margin-top: 0.5rem; 
  padding-top: 1rem;
  border-top: 1px solid #e5e7eb;
}

.dialog-actions .btn[disabled] { 
  opacity: 0.5; 
  cursor: not-allowed; 
  box-shadow: none; 
}
</style>
